#lang racket
(require rackunit)


(define (powers-of-two)
  (define (powers-of-two-aux curr-pow-of-two)
    (cons
      curr-pow-of-two
      (thunk (powers-of-two-aux (* curr-pow-of-two 2)))))
  (powers-of-two-aux 1))










; Let powers-of-two be a stream of the powers of two
; (cons 1
;    (thunk (cons 2
;      (thunk (cons 4
;         (thunk ...))))))

(define finite-powers-of
  (cons 1
    (thunk (cons 2
      (thunk (cons 4
        (thunk (error "you went too far"))))))))

finite-powers-of

(define (stream-get s)
  (car s))

(define (stream-next s)
  ((cdr s)))

(define (stream-skip s elem-count)
  (cond [(<= elem-count 0) s]
        [else (stream-skip (stream-next s) (- elem-count 1))]))

(define (stream-ref s index)
  (stream-get (stream-skip s index)))

(check-equal? 1 (stream-ref (powers-of-two) 0))                 ; the 1st element of the stream
(check-equal? 2 (stream-ref (powers-of-two) 1))         ; the 2nd element of the stream
(check-equal? 4 (stream-ref (powers-of-two) 2)) ; the 3rd element of the stream
(check-equal? 8 (stream-ref (powers-of-two) 3))
(check-equal? 16 (stream-ref (powers-of-two) 4))

(define (count-until s pred)
  (define h (car s))
  (cond [(pred h) (+ 1 (count-until ((cdr s)) pred))]
        [else 0]
  ))

(check-equal? 3 (count-until (powers-of-two) (lambda (x) (< x 8))))
(check-equal? 4 (count-until (powers-of-two) (lambda (x) (< x 10))))
(check-equal? 0 (count-until (powers-of-two) (lambda (x) (<= x 0))))
; (check-equal? 3 (count-until (powers-of-two) (curryr < 8)))  ; Reverse Currying
; (check-equal? 0 (count-until (powers-of-two) (curryr <= 0))) ; Reverse Currying



(define (const value)
  (cons value (thunk (const value))))


(check-equal? 10 (stream-ref (const 10) 0))                 ; the 1st element of the stream
(check-equal? 10 (stream-ref (const 10) 1))         ; the 2nd element of the stream
(check-equal? 10 (stream-ref (const 10) 2)) ; the 3rd element of the stream
(check-equal? 10 (stream-ref (const 10) 3))
(check-equal? 10 (stream-ref (const 10) 4))

; 0 1 2 3 4 5 ..

(define (naturals)
  (define (naturals-aux curr-nat)
    (cons curr-nat (thunk (naturals-aux (+ 1 curr-nat)))))
  (naturals-aux 0))

(check-equal? 0 (stream-ref (naturals) 0))                 ; the 1st element of the stream
(check-equal? 1 (stream-ref (naturals) 1))         ; the 2nd element of the stream
(check-equal? 2 (stream-ref (naturals) 2)) ; the 3rd element of the stream
(check-equal? 3 (stream-ref (naturals) 3))
(check-equal? 4 (stream-ref (naturals) 4))

; (map f l) = (cons (f (first l)) (map f (rest l))

(define (stream-map f s)
  (cons (f (stream-get s)) (thunk (stream-map f (stream-next s)))))

(define s0
  (stream-map (curry + 2) (naturals)))
(check-equal? (stream-get s0) 2)
(define s1 (stream-next s0))
(check-equal? (stream-get s1) 3)
(define s2 (stream-next s1))
(check-equal? (stream-get s2) 4)

; 0 1 2 3 4
; 0 2*1 2*2 2*3 ...
; 0 2 4 6 ..

(define (even-numbers)
  (stream-map (lambda (x) (* x 2)) (naturals)))



(check-equal? 0 (stream-ref (even-numbers) 0))                 ; the 1st element of the stream
(check-equal? 2 (stream-ref (even-numbers) 1))         ; the 2nd element of the stream
(check-equal? 4 (stream-ref (even-numbers) 2)) ; the 3rd element of the stream
(check-equal? 6 (stream-ref (even-numbers) 3))
(check-equal? 8 (stream-ref (even-numbers) 4))


(define (stream-zip s1 s2)
  (define p (cons (stream-get s1) (stream-get s2)))
  (define s (thunk
    (stream-zip (stream-next s1) (stream-next s2))))
  (cons p s))

(define nat-even (stream-zip (naturals) (even-numbers)))

(check-equal? (cons 0 0) (stream-ref nat-even 0))                 ; the 1st element of the stream
(check-equal? (cons 1 2) (stream-ref nat-even 1))         ; the 2nd element of the stream
(check-equal? (cons 2 4) (stream-ref nat-even 2)) ; the 3rd element of the stream
(check-equal? (cons 3 6) (stream-ref nat-even 3))
(check-equal? (cons 4 8) (stream-ref nat-even 4))

(define (stream-enum s)
  (stream-zip (naturals) s))

(define enum-even (stream-enum (even-numbers)))

(check-equal? (cons 0 0) (stream-ref enum-even 0))                 ; the 1st element of the stream
(check-equal? (cons 1 2) (stream-ref enum-even 1))         ; the 2nd element of the stream
(check-equal? (cons 2 4) (stream-ref enum-even 2)) ; the 3rd element of the stream
(check-equal? (cons 3 6) (stream-ref enum-even 3))
(check-equal? (cons 4 8) (stream-ref enum-even 4))


(define (stream-filter to-keep? s)
  (define elem (stream-get s))
  (cond [(to-keep? elem) (cons elem (thunk (stream-filter to-keep? (stream-next s))))]
        [else (stream-filter to-keep? (stream-next s))]))

(define gt-ten
  (stream-filter (curry <= 10)
    (naturals)))

(check-equal? 10 (stream-ref gt-ten 0))                 ; the 1st element of the stream
(check-equal? 11 (stream-ref gt-ten 1))         ; the 2nd element of the stream
(check-equal? 12 (stream-ref gt-ten 2)) ; the 3rd element of the stream
(check-equal? 13 (stream-ref gt-ten 3))
(check-equal? 14 (stream-ref gt-ten 4))


(define keep-all
  (stream-filter (lambda (x) #t)
    (naturals)))


(check-equal? 0 (stream-ref keep-all 0))                 ; the 1st element of the stream
(check-equal? 1 (stream-ref keep-all 1))         ; the 2nd element of the stream
(check-equal? 2 (stream-ref keep-all 2)) ; the 3rd element of the stream
(check-equal? 3 (stream-ref keep-all 3))
(check-equal? 4 (stream-ref keep-all 4))


; (define keep-none
;   (stream-filter (lambda (x) #f)
;     (naturals)))


; (check-equal? 0 (stream-ref keep-none 0))                 ; the 1st element of the stream
; (check-equal? 1 (stream-ref keep-none 1))         ; the 2nd element of the stream
; (check-equal? 2 (stream-ref keep-none 2)) ; the 3rd element of the stream
; (check-equal? 3 (stream-ref keep-none 3))
; (check-equal? 4 (stream-ref keep-none 4))



; (- 1 2)
; (* 0)
; (*)
; (/ 1 2 3)

(define (f x y z)
  (+ x y z))

(check-equal?
  (apply + (list 1 2 3 4 5))
  (+ 1 2 3 4 5))

(check-equal? 6 (apply f (list 1 2 3)))

(define (sum:v1 l)
  (cond [(empty? l) 0]
        [else (+ (first l) (sum:v1 (rest l)))]))

(check-equal?  (+ 1 2 3 4 5) (sum:v1 (list 1 2 3 4 5)))

(define (sum:v2 l)
  (apply + l))

(check-equal?  (+ 1 2 3 4 5) (sum:v2 (list 1 2 3 4 5)))


(define (vec-mult  x y . elems)
  (map (lambda (x) (* 10 x)) elems))

(vec-mult  10 20 #| . |# 30)




;